use hash_table::hash_table::HashTable;
use lazy_static::lazy_static;
use std::sync::Mutex;

lazy_static! {
    static ref ALLOCATED_KEYS: Mutex<i32> = Mutex::new(0);
    static ref ALLOCATED_VALUES: Mutex<i32> = Mutex::new(0);
}

fn generate_hash_table() -> HashTable<String, String, impl Fn(&String) -> u64, impl Fn(&String, &String) -> bool> {
    let hash_func = |k: &String| {
        let mut hash = 0u64;
        for c in k.chars() {
            hash = hash.wrapping_mul(31).wrapping_add(c as u64);
        }
        hash
    };
    let equal_func = |a: &String, b: &String| a == b;

    let mut hash_table = HashTable::new(hash_func, equal_func, 0);
    
    for i in 0..10000 {
        let key = i.to_string();
        let value = key.clone();
        hash_table.insert(key, value);
    }

    hash_table
}

#[test]
fn test_hash_table_new_free() {
    let mut hash_table = HashTable::new(
        |k: &i32| *k as u64,
        |a, b| a == b,
        0,
    );
    let values = [1, 2, 3, 4];
    for &v in &values {
        assert!(hash_table.insert(v, v));
    }
}

#[test]
fn test_hash_table_insert_lookup() {
    let mut hash_table = generate_hash_table();
    assert_eq!(hash_table.num_entries(), 10000);

    for i in 0..10000 {
        let key = i.to_string();
        assert_eq!(hash_table.lookup(&key).unwrap(), &key);
    }

    assert!(hash_table.lookup(&(-1).to_string()).is_none());
    assert!(hash_table.lookup(&10000.to_string()).is_none());

    let key = 12345.to_string();
    hash_table.insert(key.clone(), "hello world".to_string());
    assert_eq!(hash_table.lookup(&key).unwrap(), "hello world");
}

#[test]
fn test_hash_table_remove() {
    let mut hash_table = generate_hash_table();
    let key = 5000.to_string();
    assert!(hash_table.remove(&key));
    assert_eq!(hash_table.num_entries(), 9999);
    assert!(hash_table.lookup(&key).is_none());

    let invalid_key = (-1).to_string();
    assert!(!hash_table.remove(&invalid_key));
    assert_eq!(hash_table.num_entries(), 9999);
}

#[test]
#[test]
fn test_hash_table_iterating() {
    let hash_table = generate_hash_table();
    let mut count = 0;

    let mut iter = hash_table.iter();
    while let Some(_) = iter.next() {
        count += 1;
    }
    assert_eq!(count, 10000);

    let empty_table: HashTable<i32, i32, _, _> = HashTable::new(
        |k: &i32| *k as u64,
        |a: &i32, b: &i32| a == b,
        0,
    );
    assert!(empty_table.iter().next().is_none());
}

#[test]
fn test_hash_table_iterating_remove() {
    let mut hash_table = generate_hash_table();
    let mut keys_to_remove = Vec::new();

    let mut iter = hash_table.iter();
    while let Some(pair) = iter.next() {
        let num: i32 = pair.key.parse().unwrap();
        if num % 100 == 0 {
            keys_to_remove.push(pair.key.clone());
        }
    }

    let removed = keys_to_remove.len();
    for key in keys_to_remove {
        assert!(hash_table.remove(&key));
    }
    assert_eq!(hash_table.num_entries(), (10000 - removed) as u32);

    for i in 0..10000 {
        let key = i.to_string();
        if i % 100 == 0 {
            assert!(hash_table.lookup(&key).is_none());
        } else {
            assert!(hash_table.lookup(&key).is_some());
        }
    }
    assert_eq!(hash_table.num_entries(), 10000 - 100);
}

fn new_key(value: i32) -> Box<i32> {
    *ALLOCATED_KEYS.lock().unwrap() += 1;
    Box::new(value)
}

fn free_key(key: Box<i32>) {
    *ALLOCATED_KEYS.lock().unwrap() -= 1;
}